/* Copyright (C) 2014 InfiniDB, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; version 2 of
   the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA. */

/******************************************************************************************
 * $Id: we_cache.cpp 33 2006-10-30 13:45:13Z wzhou  $
 *
 ******************************************************************************************/
/** @file */

#include <we_cache.h>

using namespace std;

namespace WriteEngine
{
CacheControl* Cache::m_cacheParam = NULL;
FreeBufList* Cache::m_freeList = NULL;
CacheMap* Cache::m_lruList = NULL;
CacheMap* Cache::m_writeList = NULL;
    bool Cache::m_useCache = false;
/***********************************************************
 * DESCRIPTION:
 *    Clear all list and free memory
 * PARAMETERS:
 *    none
 * RETURN:
 *    NO_ERROR if success, other otherwise
 ***********************************************************/
void Cache::clear()
{
  CacheMapIt it;
  BlockBuffer* block;
  size_t i;

  // free list
  if (m_freeList != NULL)
  {
    for (i = 0; i < m_freeList->size(); i++)
    {
      block = m_freeList->at(i);
      block->clear();
    }
  }

  // LRU list
  if (m_lruList != NULL)
  {
    for (it = m_lruList->begin(); it != m_lruList->end(); it++)
    {
      block = it->second;
      block->clear();
      m_freeList->push_back(block);
    }

    m_lruList->clear();
  }

  // Write list
  if (m_writeList != NULL)
  {
    for (it = m_writeList->begin(); it != m_writeList->end(); it++)
    {
      block = it->second;
      block->clear();
      m_freeList->push_back(block);
    }

    m_writeList->clear();
  }
}

/***********************************************************
 * DESCRIPTION:
 *    Flush write cache
 * PARAMETERS:
 *    none
 * RETURN:
 *    NO_ERROR if success, other otherwise
 ***********************************************************/
int Cache::flushCache()
{
  bool bHasReadBlock = false;
  BlockBuffer* curBuf;

  // add lock here
  if (m_lruList && m_lruList->size() > 0)
  {
    bHasReadBlock = true;

    for (CacheMapIt it = m_lruList->begin(); it != m_lruList->end(); it++)
    {
      curBuf = it->second;
      curBuf->clear();
      m_freeList->push_back(curBuf);
    }

    m_lruList->clear();
  }

  // must write to disk first
  if (m_writeList && m_writeList->size() > 0)
  {
    if (!bHasReadBlock)
      for (CacheMapIt it = m_writeList->begin(); it != m_writeList->end(); it++)
      {
        curBuf = it->second;
        curBuf->clear();
        m_freeList->push_back(curBuf);
      }
    else
      for (CacheMapIt it = m_writeList->begin(); it != m_writeList->end(); it++)
      {
        curBuf = it->second;
        (*curBuf).block.dirty = false;
        processCacheMap(m_lruList, curBuf, INSERT);
      }

    m_writeList->clear();

  }  // end of if( m_writeList->size()

  // add unlock here
  return NO_ERROR;
}

/***********************************************************
 * DESCRIPTION:
 *    Free memory
 * PARAMETERS:
 *    none
 * RETURN:
 *    NO_ERROR if success, other otherwise
 ***********************************************************/
void Cache::freeMemory()
{
  CacheMapIt it;
  BlockBuffer* block;
  size_t i;

  // free list
  if (m_freeList != NULL)
  {
    for (i = 0; i < m_freeList->size(); i++)
    {
      block = m_freeList->at(i);
      block->freeMem();
      delete block;
    }

    m_freeList->clear();
    delete m_freeList;
    m_freeList = NULL;
  }

  // LRU list
  if (m_lruList != NULL)
  {
    for (it = m_lruList->begin(); it != m_lruList->end(); it++)
    {
      block = it->second;
      block->freeMem();
      delete block;
    }

    m_lruList->clear();
    delete m_lruList;
    m_lruList = NULL;
  }

  // Write list
  if (m_writeList != NULL)
  {
    for (it = m_writeList->begin(); it != m_writeList->end(); it++)
    {
      block = it->second;
      block->freeMem();
      delete block;
    }

    m_writeList->clear();
    delete m_writeList;
    m_writeList = NULL;
  }

  // param
  if (m_cacheParam != NULL)
  {
    delete m_cacheParam;
    m_cacheParam = NULL;
  }
}

/***********************************************************
 * DESCRIPTION:
 *    get a list size
 * PARAMETERS:
 *    listType - List type
 * RETURN:
 *    NO_ERROR if success, other otherwise
 ***********************************************************/
int Cache::getListSize(const CacheListType listType)
{
  int size = 0;

  if (!m_useCache)
    return size;

  switch (listType)
  {
    case FREE_LIST: size = m_freeList->size(); break;

    case LRU_LIST: size = m_lruList->size(); break;

    case WRITE_LIST:
    default: size = m_writeList->size(); break;
  }

  return size;
}

/***********************************************************
 * DESCRIPTION:
 *    Init all parameters and list
 * PARAMETERS:
 *    totalBlock - total blocks
 *    chkPoint - checkpoint interval
 *    pctFree - percentage free
 * RETURN:
 *    NO_ERROR if success, other otherwise
 ***********************************************************/
void Cache::init(const int totalBlock, const int chkPoint, const int pctFree)
{
  BlockBuffer* buffer;

  if (m_cacheParam && m_freeList && m_lruList && m_writeList)
    return;

  m_cacheParam = new CacheControl();
  m_cacheParam->totalBlock = totalBlock;
  m_cacheParam->checkInterval = chkPoint;
  m_cacheParam->pctFree = pctFree;

  m_freeList = new FreeBufList();
  m_lruList = new CacheMap();
  m_writeList = new CacheMap();

  for (int i = 0; i < m_cacheParam->totalBlock; i++)
  {
    buffer = new BlockBuffer();
    buffer->init();
    m_freeList->push_back(buffer);
  }
}

/***********************************************************
 * DESCRIPTION:
 *    Insert a buffer to LRU list
 * PARAMETERS:
 *    cb - Comm Block
 *    lbid - lbid value
 *    fbo - fbo
 *    buf - input buffer
 * RETURN:
 *    NO_ERROR if success, other otherwise
 ***********************************************************/
int Cache::insertLRUList(CommBlock& cb, const uint64_t lbid, const uint64_t fbo, const unsigned char* buf)
{
  BlockBuffer* buffer;
  vector<BlockBuffer*>::iterator it;

  if (m_freeList->size() == 0)
    return ERR_FREE_LIST_EMPTY;

  // make sure flush first if necessary
  it = m_freeList->begin();
  buffer = *it;
  memcpy((*buffer).block.data, buf, BYTE_PER_BLOCK);
  (*buffer).listType = LRU_LIST;
  (*buffer).block.lbid = lbid;
  (*buffer).block.fbo = fbo;
  (*buffer).block.dirty = false;
  (*buffer).block.hitCount = 1;
  (*buffer).cb.file.oid = cb.file.oid;
  (*buffer).cb.file.pFile = cb.file.pFile;

  RETURN_ON_ERROR(processCacheMap(m_lruList, buffer, INSERT));
  m_freeList->erase(it);

  return NO_ERROR;
}

/***********************************************************
 * DESCRIPTION:
 *    Load cache block
 * PARAMETERS:
 *    key - Cache key
 *    buf - output buffer
 * RETURN:
 *    NO_ERROR if success, other otherwise
 ***********************************************************/
int Cache::loadCacheBlock(const CacheKey& key, unsigned char* buf)
{
  BlockBuffer* buffer;
  CacheMapIt iter;

  iter = m_lruList->find(key);

  if (iter != m_lruList->end())
    buffer = iter->second;
  else
  {
    iter = m_writeList->find(key);

    if (iter != m_writeList->end())
      buffer = iter->second;
    else
      return ERR_CACHE_KEY_NOT_EXIST;
  }

  memcpy(buf, (*buffer).block.data, BYTE_PER_BLOCK);
  (*buffer).block.hitCount++;

  return NO_ERROR;
}

/***********************************************************
 * DESCRIPTION:
 *    Modify cache block
 * PARAMETERS:
 *    key - Cache key
 *    buf - output buffer
 * RETURN:
 *    NO_ERROR if success, other otherwise
 ***********************************************************/
int Cache::modifyCacheBlock(const CacheKey& key, const unsigned char* buf)
{
  BlockBuffer* buffer;
  CacheMapIt iter;

  iter = m_lruList->find(key);

  if (iter != m_lruList->end())
  {
    buffer = iter->second;
    (*buffer).listType = WRITE_LIST;
    (*buffer).block.dirty = true;

    (*m_writeList)[key] = iter->second;
    m_lruList->erase(iter);
  }
  else
  {
    iter = m_writeList->find(key);

    if (iter != m_writeList->end())
      buffer = iter->second;
    else
      return ERR_CACHE_KEY_NOT_EXIST;
  }

  memcpy((*buffer).block.data, buf, BYTE_PER_BLOCK);
  (*buffer).block.hitCount++;

  return NO_ERROR;
}

/***********************************************************
 * DESCRIPTION:
 *    Print cache list
 * PARAMETERS:
 *    none
 * RETURN:
 *    none
 ***********************************************************/
void Cache::printCacheList()
{
  BlockBuffer* buffer;
  int i = 0;

  if (!m_useCache)
    return;

  cout << "\nFree List has " << m_freeList->size() << " elements" << endl;
  cout << "LRU List has " << m_lruList->size() << " elements" << endl;

  for (CacheMapIt it = m_lruList->begin(); it != m_lruList->end(); it++)
  {
    buffer = it->second;
    cout << "\t[" << i++ << "] key=" << it->first << " listType=" << buffer->listType
         << " oid=" << (*buffer).cb.file.oid << " fbo=" << (*buffer).block.fbo
         << " dirty=" << (*buffer).block.dirty << " hitCount=" << (*buffer).block.hitCount << endl;
  }

  i = 0;
  cout << "Write List has " << m_writeList->size() << " elements" << endl;

  for (CacheMapIt it = m_writeList->begin(); it != m_writeList->end(); it++)
  {
    buffer = it->second;
    cout << "\t[" << i++ << "] key=" << it->first << " listType=" << buffer->listType
         << " oid=" << (*buffer).cb.file.oid << " fbo=" << (*buffer).block.fbo
         << " dirty=" << (*buffer).block.dirty << " hitCount=" << (*buffer).block.hitCount << endl;
  }
}

/***********************************************************
 * DESCRIPTION:
 *    Process a buffer in a cache map
 * PARAMETERS:
 *    buffer - block buffer
 *    opType - insert or delete
 * RETURN:
 *    NO_ERROR if success, other otherwise
 ***********************************************************/
int Cache::processCacheMap(CacheMap* map, BlockBuffer* buffer, OpType opType)
{
  RETURN_ON_NULL(buffer, ERR_NULL_BLOCK);
  CacheMapIt iter;

  CacheKey key = getCacheKey(buffer);
  iter = map->find(key);

  // only handle insert and delete
  if (iter == map->end())
  {
    if (opType == INSERT)
      (*map)[key] = buffer;
    else
      return ERR_CACHE_KEY_NOT_EXIST;
  }
  else
  {
    if (opType == INSERT)
      return ERR_CACHE_KEY_EXIST;
    else
      map->erase(iter);
  }

  return NO_ERROR;
}

}  // namespace WriteEngine
